package com.vst.orm.dao.hibernate;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hibernate.Criteria;
import org.hibernate.NonUniqueResultException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;

import com.vst.orm.search.ExampleOptions;
import com.vst.orm.search.Filter;
import com.vst.orm.search.ISearch;
import com.vst.orm.search.SearchResult;
import com.vst.orm.search.hibernate.HibernateMetadataUtil;
import com.vst.orm.search.hibernate.HibernateSearchProcessor;

public class HibernateBaseDAO {
	private HibernateSearchProcessor searchProcessor;
	private SessionFactory sessionFactory;
	private HibernateMetadataUtil metaDataUtil;

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
		this.searchProcessor = HibernateSearchProcessor.getInstanceForSessionFactory(sessionFactory);
		this.metaDataUtil = HibernateMetadataUtil.getInstanceForSessionFactory(sessionFactory);
	}

	protected SessionFactory getSessionFactory() {
		return this.sessionFactory;
	}

	protected Session getSession() {
		return this.sessionFactory.getCurrentSession();
	}

	protected HibernateMetadataUtil getMetaDataUtil() {
		return this.metaDataUtil;
	}

	protected HibernateSearchProcessor getSearchProcessor() {
		return this.searchProcessor;
	}

	protected Serializable _save(Object entity) {
		return getSession().save(entity);
	}

	protected void _save(Object... entities) {
		for (Object entity : entities) {
			_save(entity);
		}
	}

	protected void _saveOrUpdate(Object entity) {
		getSession().saveOrUpdate(entity);
	}

	protected boolean _saveOrUpdateIsNew(Object entity) {
		if (entity == null) {
			throw new IllegalArgumentException("attempt to saveOrUpdate with null entity");
		}
		Serializable id = getMetaDataUtil().getId(entity);
		if (getSession().contains(entity)) {
			return false;
		}
		if ((id == null) || (new Long(0L).equals(id)) || (!_exists(entity))) {
			_save(entity);
			return true;
		}
		_update(new Object[] { entity });
		return false;
	}

	protected boolean[] _saveOrUpdateIsNew(Object... entities) {
		Boolean[] exists = new Boolean[entities.length];
		for (int i = 0; i < entities.length; i++) {
			if (entities[i] == null) {
				throw new IllegalArgumentException("attempt to saveOrUpdate with null entity");
			}
			if (getSession().contains(entities[i])) {
				exists[i] = Boolean.valueOf(true);
			} else {
				Serializable id = getMetaDataUtil().getId(entities[i]);
				if ((id == null) || (new Long(0L).equals(id))) {
					exists[i] = Boolean.valueOf(false);
				}
			}
		}
		Map<Class<?>, List<Integer>> mayExist = new HashMap();
		for (int i = 0; i < entities.length; i++) {
			if (exists[i] == null) {
				Class<?> entityClass = this.metaDataUtil.getUnproxiedClass(entities[i]);
				List<Integer> l = (List) mayExist.get(entityClass);
				if (l == null) {
					l = new ArrayList();
					mayExist.put(entityClass, l);
				}
				l.add(Integer.valueOf(i));
			}
		}
		for (Map.Entry<Class<?>, List<Integer>> entry : mayExist.entrySet()) {
			Serializable[] ids = new Serializable[((List) entry.getValue()).size()];
			for (int i = 0; i < ids.length; i++) {
				ids[i] = getMetaDataUtil().getId(entities[((Integer) ((List) entry.getValue()).get(i)).intValue()]);
			}
			boolean[] exists2 = _exists((Class) entry.getKey(), ids);
			for (int i = 0; i < ids.length; i++) {
				exists[((Integer) ((List) entry.getValue()).get(i)).intValue()] = Boolean.valueOf(exists2[i]);
			}
		}
		boolean[] isNew = new boolean[entities.length];
		for (int i = 0; i < entities.length; i++) {
			if (entities[i] != null) {
				if (exists[i].booleanValue()) {
					_update(new Object[] { entities[i] });
					isNew[i] = false;
				} else {
					_save(entities[i]);
					isNew[i] = true;
				}
			}
		}
		return isNew;
	}

	protected void _persist(Object... entities) {
		for (Object entity : entities) {
			getSession().persist(entity);
		}
	}

	protected boolean _deleteById(Class<?> type, Serializable id) {
		if (id != null) {
			type = this.metaDataUtil.getUnproxiedClass(type);
			Object entity = getSession().get(type, id);
			if (entity != null) {
				getSession().delete(entity);
				return true;
			}
		}
		return false;
	}

	protected void _deleteById(Class<?> type, Serializable... ids) {
		type = this.metaDataUtil.getUnproxiedClass(type);
		Criteria c = getSession().createCriteria(type);
		c.add(Restrictions.in("id", ids));
		for (Object entity : c.list()) {
			getSession().delete(entity);
		}
	}

	protected boolean _deleteEntity(Object entity) {
		if (entity != null) {
			Serializable id = getMetaDataUtil().getId(entity);
			if (id != null) {
				entity = getSession().get(this.metaDataUtil.getUnproxiedClass(entity), id);
				if (entity != null) {
					getSession().delete(entity);
					return true;
				}
			}
		}
		return false;
	}

	protected void _deleteEntities(Object... entities) {
		for (Object entity : entities) {
			if (entity != null) {
				getSession().delete(entity);
			}
		}
	}

	protected <T> T _get(Class<T> type, Serializable id) {
		type = this.metaDataUtil.getUnproxiedClass(type);
		return (T) getSession().get(type, id);
	}

	protected <T> T[] _get(Class<T> type, Serializable... ids) {
		type = this.metaDataUtil.getUnproxiedClass(type);
		Criteria c = getSession().createCriteria(type);
		c.add(Restrictions.in("id", ids));
		Object[] retVal = (Object[]) Array.newInstance(type, ids.length);
		for (Object entity : c.list()) {
			Serializable id = getMetaDataUtil().getId(entity);
			for (int i = 0; i < ids.length; i++) {
				if (id.equals(ids[i])) {
					retVal[i] = entity;
					break;
				}
			}
		}
		return (T[]) retVal;
	}

	protected <T> T _load(Class<T> type, Serializable id) {
		type = this.metaDataUtil.getUnproxiedClass(type);
		return (T) getSession().load(type, id);
	}

	protected <T> T[] _load(Class<T> type, Serializable... ids) {
		type = this.metaDataUtil.getUnproxiedClass(type);
		Object[] retVal = (Object[]) Array.newInstance(type, ids.length);
		for (int i = 0; i < ids.length; i++) {
			if (ids[i] != null) {
				retVal[i] = _load(type, ids[i]);
			}
		}
		return (T[]) retVal;
	}

	protected void _load(Object transientEntity, Serializable id) {
		getSession().load(transientEntity, id);
	}

	protected <T> List<T> _all(Class<T> type) {
		type = this.metaDataUtil.getUnproxiedClass(type);
		return getSession().createCriteria(type).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
	}

	protected void _update(Object... transientEntities) {
		for (Object entity : transientEntities) {
			getSession().update(entity);
		}
	}

	protected <T> T _merge(T entity) {
		return (T) getSession().merge(entity);
	}

	protected List _search(ISearch search) {
		if (search == null) {
			throw new NullPointerException("Search is null.");
		}
		if (search.getSearchClass() == null) {
			throw new NullPointerException("Search class is null.");
		}
		return getSearchProcessor().search(getSession(), search);
	}

	protected List _search(Class<?> searchClass, ISearch search) {
		if (search == null) {
			throw new NullPointerException("Search is null.");
		}
		if (searchClass == null) {
			throw new NullPointerException("Search class is null.");
		}
		if ((search.getSearchClass() != null) && (!search.getSearchClass().equals(searchClass))) {
			throw new IllegalArgumentException("Search class does not match expected type: " + searchClass.getName());
		}
		return getSearchProcessor().search(getSession(), searchClass, search);
	}

	protected int _count(ISearch search) {
		if (search == null) {
			throw new NullPointerException("Search is null.");
		}
		if (search.getSearchClass() == null) {
			throw new NullPointerException("Search class is null.");
		}
		return getSearchProcessor().count(getSession(), search);
	}

	protected int _count(Class<?> searchClass, ISearch search) {
		if (search == null) {
			throw new NullPointerException("Search is null.");
		}
		if (searchClass == null) {
			throw new NullPointerException("Search class is null.");
		}
		if ((search.getSearchClass() != null) && (!search.getSearchClass().equals(searchClass))) {
			throw new IllegalArgumentException("Search class does not match expected type: " + searchClass.getName());
		}
		return getSearchProcessor().count(getSession(), searchClass, search);
	}

	protected int _count(Class<?> type) {
		List counts = getSession().createQuery("select count(*) from " + type.getName()).list();
		int sum = 0;
		for (Object count : counts) {
			sum += ((Long) count).intValue();
		}
		return sum;
	}

	protected SearchResult _searchAndCount(ISearch search) {
		if (search == null) {
			throw new NullPointerException("Search is null.");
		}
		if (search.getSearchClass() == null) {
			throw new NullPointerException("Search class is null.");
		}
		return getSearchProcessor().searchAndCount(getSession(), search);
	}

	protected SearchResult _searchAndCount(Class<?> searchClass, ISearch search) {
		if (search == null) {
			throw new NullPointerException("Search is null.");
		}
		if (searchClass == null) {
			throw new NullPointerException("Search class is null.");
		}
		if ((search.getSearchClass() != null) && (!search.getSearchClass().equals(searchClass))) {
			throw new IllegalArgumentException("Search class does not match expected type: " + searchClass.getName());
		}
		return getSearchProcessor().searchAndCount(getSession(), searchClass, search);
	}

	protected Object _searchUnique(ISearch search) throws NonUniqueResultException {
		if (search == null) {
			throw new NullPointerException("Search is null.");
		}
		if (search.getSearchClass() == null) {
			throw new NullPointerException("Search class is null.");
		}
		return getSearchProcessor().searchUnique(getSession(), search);
	}

	protected Object _searchUnique(Class<?> searchClass, ISearch search) {
		if (search == null) {
			throw new NullPointerException("Search is null.");
		}
		if (searchClass == null) {
			throw new NullPointerException("Search class is null.");
		}
		if ((search.getSearchClass() != null) && (!search.getSearchClass().equals(searchClass))) {
			throw new IllegalArgumentException("Search class does not match expected type: " + searchClass.getName());
		}
		return getSearchProcessor().searchUnique(getSession(), searchClass, search);
	}

	protected boolean _sessionContains(Object o) {
		return getSession().contains(o);
	}

	protected void _flush() {
		getSession().flush();
	}

	protected void _refresh(Object... entities) {
		for (Object entity : entities) {
			getSession().refresh(entity);
		}
	}

	protected boolean _exists(Object entity) {
		if (getSession().contains(entity)) {
			return true;
		}
		return _exists(entity.getClass(), getMetaDataUtil().getId(entity));
	}

	protected boolean _exists(Class<?> type, Serializable id) {
		if (type == null) {
			throw new NullPointerException("Type is null.");
		}
		if (id == null) {
			return false;
		}
		type = this.metaDataUtil.getUnproxiedClass(type);

		Query query = getSession().createQuery("select id from " + type.getName() + " where id = :id");
		query.setParameter("id", id);
		return query.list().size() == 1;
	}

	protected boolean[] _exists(Class<?> type, Serializable... ids) {
		if (type == null) {
			throw new NullPointerException("Type is null.");
		}
		type = this.metaDataUtil.getUnproxiedClass(type);

		boolean[] ret = new boolean[ids.length];

		StringBuilder sb = new StringBuilder("select id from " + type.getName() + " where");
		boolean first = true;
		for (int i = 0; i < ids.length; i++) {
			if (first) {
				first = false;
				sb.append(" id = :id");
			} else {
				sb.append(" or id = :id");
			}
			sb.append(i);
		}
		Query query = getSession().createQuery(sb.toString());
		for (int i = 0; i < ids.length; i++) {
			query.setParameter("id" + i, ids[i]);
		}
		/*
		for (Serializable id : query.list()) {
			for (int i = 0; i < ids.length; i++) {
				if (id.equals(ids[i])) {
					ret[i] = true;
				}
			}
		}
		*/
		return ret;
	}

	protected Filter _getFilterFromExample(Object example) {
		return this.searchProcessor.getFilterFromExample(example);
	}

	protected Filter _getFilterFromExample(Object example, ExampleOptions options) {
		return this.searchProcessor.getFilterFromExample(example, options);
	}
}
